package io.gitee.zhangbinhub.acp.boot.websocket.reactive

import io.gitee.zhangbinhub.acp.boot.component.BootLogAdapter
import io.gitee.zhangbinhub.acp.boot.log.LogAdapter
import io.gitee.zhangbinhub.acp.boot.tools.HttpTools
import io.gitee.zhangbinhub.acp.boot.tools.IpTools
import org.springframework.util.LinkedMultiValueMap
import org.springframework.util.MultiValueMap
import org.springframework.web.reactive.socket.WebSocketSession
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.nio.ByteBuffer
import java.util.concurrent.ConcurrentHashMap

abstract class AcpWebSocketServerReactive @JvmOverloads constructor(
    logAdapter: LogAdapter,
    private val clientIdKey: String? = null,
    private val clientIdIndex: Int? = null
) : AcpWebSocketHandlerReactive(logAdapter) {
    @JvmField
    protected var clientId: String? = null

    @JvmField
    protected var clientIp: String? = null

    @JvmField
    protected var queryMap: MultiValueMap<String, String> = LinkedMultiValueMap()
    abstract fun getPath(): String
    override fun handle(session: WebSocketSession): Mono<Void> {
        this.session = session
        this.queryMap = HttpTools.getQueryMap(session.handshakeInfo.uri)
        if (clientIdIndex != null) {
            if (this.clientId == null) {
                this.clientId = HttpTools.getPathVariable(session.handshakeInfo.uri, clientIdIndex)
            }
        }
        if (clientIdKey != null) {
            if (this.clientId == null) {
                this.clientId = queryMap.getFirst(clientIdKey)
            }
            if (this.clientId == null) {
                this.clientId = session.handshakeInfo.headers[clientIdKey]?.first()
            }
        }
        return if (this.clientId == null) {
            logAdapter.error("websocket client id is null")
            session.close().also {
                sessionClose(session)
            }
        } else {
            acpWebSocketInstanceMap[clientId!!] = this
            this.clientIp = IpTools.getRemoteIP(session)
            val input = doReceiveMessage(session)
            val output = session.send(Flux.create { fluxSink ->
                this.outFluxSink = fluxSink
                sessionOpen(session)
            })
            Flux.merge(input, output).doFinally {
                acpWebSocketInstanceMap.remove(clientId)
                sessionClose(session)
            }.then()
        }
    }

    companion object {
        private val log: LogAdapter = BootLogAdapter()
        private val acpWebSocketInstanceMap: ConcurrentHashMap<String, AcpWebSocketServerReactive> =
            ConcurrentHashMap<String, AcpWebSocketServerReactive>()

        @JvmStatic
        fun getInstance(clientId: String): AcpWebSocketServerReactive? = acpWebSocketInstanceMap[clientId]

        @JvmStatic
        fun getInstanceList(): List<AcpWebSocketServerReactive> =
            acpWebSocketInstanceMap.map { entry -> entry.value }.apply {
                if (this.isEmpty()) {
                    log.warn("WebSocket does not have any clients!")
                }
            }

        /**
         * 主动发送消息到客户端
         * @param message 消息
         * @param clientId 客户端唯一标识
         */
        @JvmStatic
        fun sendMessageToClient(message: ByteBuffer, clientId: String) {
            getInstance(clientId).let { instance ->
                if (instance != null) {
                    instance.sendMessage(message)
                } else {
                    log.error("WebSocket client[$clientId] is not found!")
                }
            }
        }

        /**
         * 主动发送消息到客户端
         * @param message 消息
         * @param clientId 客户端唯一标识
         */
        @JvmStatic
        fun sendMessageToClient(message: String, clientId: String) {
            getInstance(clientId).let { instance ->
                if (instance != null) {
                    instance.sendMessage(message)
                } else {
                    log.error("WebSocket client[$clientId] is not found!")
                }
            }
        }

        /**
         * 主动发送消息到客户端
         * @param message 消息
         */
        @JvmStatic
        fun sendMessageToClient(message: ByteBuffer) {
            getInstanceList().map { instance -> instance.sendMessage(message) }
        }

        /**
         * 主动发送消息到客户端
         * @param message 消息
         */
        @JvmStatic
        fun sendMessageToClient(message: String) {
            getInstanceList().map { instance -> instance.sendMessage(message) }
        }
    }
}